﻿// -----------------------------------------------------------------------
// <copyright file="StickyWindow.cs" company="Abyssalsoft">
// Author:  Corneliu Tusnea
// </copyright>
// -----------------------------------------------------------------------

namespace Gui.BaseForm
{
    using System;
    using System.Collections;
    using System.Drawing;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;

    /// <summary>
    /// A windows that Sticks to other windows of the same type when moved or resized.
    /// You get a nice way of organizing multiple top-level windows.
    /// Quite similar with WinAmp 2.x style of sticking the windows
    /// </summary>
    public class StickyWindow : System.Windows.Forms.NativeWindow
    {
        #region Private members

        /// <summary>Global List of registered StickyWindows</summary>
        private static ArrayList globalStickyWindows = new ArrayList();

        /// <summary>The distance to stick</summary>
        private static int stickGap = 10;

        /// <summary>Message processor</summary>
        private ProcessMessage messageProcessor;

        /// <summary>Messages processors based on type: Default</summary>
        private ProcessMessage defaultMessageProcessor;

        /// <summary>Messages processors based on type: Move</summary>
        private ProcessMessage moveMessageProcessor;

        /// <summary>Messages processors based on type: Resize</summary>
        private ProcessMessage resizeMessageProcessor;

        /// <summary>This is true when a form is moving</summary>
        private bool movingForm;

        /// <summary>Calculated offset rectangle to be added !! (min distances in all directions!!)</summary>
        private Point formOffsetPoint;

        /// <summary>Primary offset</summary>
        private Point offsetPoint;

        /// <summary>This is true when a form is resizing</summary>
        private bool resizingForm;

        /// <summary>Resize direction</summary>
        private ResizeDir resizeDirection;

        /// <summary>Calculated rectangle to fix the size</summary>
        private Rectangle formOffsetRect;

        /// <summary>Mouse position</summary>
        private Point mousePoint;

        /// <summary>The form</summary>
        private Form originalForm;

        /// <summary>Form bounds</summary>
        private Rectangle formRect;

        /// <summary>Bounds before last operation started</summary>
        private Rectangle formOriginalRect;

        /// <summary>Stick on resize</summary>
        private bool stickOnResize;

        /// <summary>Stick on move</summary>
        private bool stickOnMove;

        /// <summary>Stick to screen borders</summary>
        private bool stickToScreen;

        /// <summary>Stick to other windows</summary>
        private bool stickToOther;

        #endregion

        #region StickyWindow Constructor

        /// <summary>
        /// Initializes a new instance of the StickyWindow class.
        /// </summary>
        /// <param name="form">Form to be made sticky</param>
        public StickyWindow(Form form)
        {
            this.resizingForm = false;
            this.movingForm = false;

            this.originalForm = form;

            this.formRect = Rectangle.Empty;
            this.formOffsetRect = Rectangle.Empty;

            this.formOffsetPoint = Point.Empty;
            this.offsetPoint = Point.Empty;
            this.mousePoint = Point.Empty;

            this.stickOnMove = true;
            this.stickOnResize = true;
            this.stickToScreen = true;
            this.stickToOther = true;

            this.defaultMessageProcessor = new ProcessMessage(this.DefaultMsgProcessor);
            this.moveMessageProcessor = new ProcessMessage(this.MoveMsgProcessor);
            this.resizeMessageProcessor = new ProcessMessage(this.ResizeMsgProcessor);
            this.messageProcessor = this.defaultMessageProcessor;

            this.AssignHandle(this.originalForm.Handle);
        }

        #endregion

        #region Delegates

        /// <summary>
        /// Internal Message Processor
        /// </summary>
        /// <param name="m">The message</param>
        /// <returns>Returns the message by reference</returns>
        private delegate bool ProcessMessage(ref Message m);

        #endregion

        #region Enums

        /// <summary>Resize direction</summary>
        private enum ResizeDir
        {
            /// <summary>Resize direction</summary>
            Top = 2,

            /// <summary>Resize direction</summary>
            Bottom = 4,

            /// <summary>Resize direction</summary>
            Left = 8,

            /// <summary>Resize direction</summary>
            Right = 16
        }

        #endregion

        #region Public operations and properties

        /// <summary>
        /// Gets or sets the distance in pixels between two forms or a form and the screen where the sticking should start
        /// Default value = 20
        /// </summary>
        public int StickGap
        {
            get { return stickGap; }
            set { stickGap = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the form to stick while resizing. Default value = true
        /// </summary>
        public bool StickOnResize
        {
            get { return this.stickOnResize; }
            set { this.stickOnResize = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the form to stick while moving. Default value = true
        /// </summary>
        public bool StickOnMove
        {
            get { return this.stickOnMove; }
            set { this.stickOnMove = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether to allow sticking to Screen Margins. Default value = true
        /// </summary>
        public bool StickToScreen
        {
            get { return this.stickToScreen; }
            set { this.stickToScreen = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether to allow sticking to other StickWindows. Default value = true
        /// </summary>
        public bool StickToOther
        {
            get { return this.stickToOther; }
            set { this.stickToOther = value; }
        }

        /// <summary>
        /// Register a new form as an external reference form.
        /// All Sticky windows will try to stick to the external references
        /// Use this to register your MainFrame so the child windows try to stick to it, when your MainFrame is NOT a sticky window
        /// </summary>
        /// <param name="frmExternal">External window that will be used as reference</param>
        public static void RegisterExternalReferenceForm(Form frmExternal)
        {
            globalStickyWindows.Add(frmExternal);
        }

        /// <summary>
        /// Unregister a form from the external references.
        /// <see cref="RegisterExternalReferenceForm"/>
        /// </summary>
        /// <param name="frmExternal">External window that will was used as reference</param>
        public static void UnregisterExternalReferenceForm(Form frmExternal)
        {
            globalStickyWindows.Remove(frmExternal);
        }

        #endregion

        #region OnHandleChange

        /// <summary>
        /// Add or remove the sticky window when the handle changes
        /// </summary>
        [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
        protected override void OnHandleChange()
        {
            if ((int)this.Handle != 0)
            {
                globalStickyWindows.Add(this.originalForm);
            }
            else
            {
                globalStickyWindows.Remove(this.originalForm);
            }
        }

        #endregion

        #region WndProc

        /// <summary>
        /// Filter the messages
        /// </summary>
        /// <param name="m">The message</param>
        [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
        [System.Diagnostics.DebuggerStepThrough()]
        protected override void WndProc(ref Message m)
        {
            if (!this.messageProcessor(ref m))
            {
                base.WndProc(ref m);
            }
        }

        #endregion

        #region DefaultMsgProcessor

        /// <summary>
        /// Processes messages during normal operations (while the form is not resized or moved)
        /// </summary>
        /// <param name="m">The message</param>
        /// <returns>Returns true if the form is moving or resizing</returns>
        [System.Diagnostics.DebuggerStepThrough()]
        private bool DefaultMsgProcessor(ref Message m)
        {
            switch (m.Msg)
            {
                case Win32.WM.WMNCLBUTTONDOWN:
                    {
                        this.originalForm.Activate();
                        this.mousePoint.X = (short)Win32.Bit.LoWord((int)m.LParam);
                        this.mousePoint.Y = (short)Win32.Bit.HiWord((int)m.LParam);
                        if (this.OnNCLButtonDown((int)m.WParam, this.mousePoint))
                        {
                            ////m.Result = new IntPtr ( (resizingForm || movingForm) ? 1 : 0 );
                            m.Result = (IntPtr)((this.resizingForm || this.movingForm) ? 1 : 0);
                            return true;
                        }

                        break;
                    }
            }

            return false;
        }

        #endregion

        #region OnNCLButtonDown

        /// <summary>
        /// Checks where the click was in the NC area and starts move or resize operation
        /// </summary>
        /// <param name="iHitTest">Integer hit</param>
        /// <param name="point">Mouse point</param>
        /// <returns>Returns resize direction</returns>
        private bool OnNCLButtonDown(int iHitTest, Point point)
        {
            Rectangle rParent = this.originalForm.Bounds;
            this.offsetPoint = point;

            switch (iHitTest)
            {
                case Win32.HT.HTCAPTION:
                    {
                        // Request for move
                        if (this.stickOnMove)
                        {
                            this.offsetPoint.Offset(-rParent.Left, -rParent.Top);
                            this.StartMove();
                            return true;
                        }
                        else
                        {
                            // Leave default processing
                            return false;
                        }
                    }

                // Requests for resize
                case Win32.HT.HTTOPLEFT:
                    return this.StartResize(ResizeDir.Top | ResizeDir.Left);
                case Win32.HT.HTTOP:
                    return this.StartResize(ResizeDir.Top);
                case Win32.HT.HTTOPRIGHT:
                    return this.StartResize(ResizeDir.Top | ResizeDir.Right);
                case Win32.HT.HTRIGHT:
                    return this.StartResize(ResizeDir.Right);
                case Win32.HT.HTBOTTOMRIGHT:
                    return this.StartResize(ResizeDir.Bottom | ResizeDir.Right);
                case Win32.HT.HTBOTTOM:
                    return this.StartResize(ResizeDir.Bottom);
                case Win32.HT.HTBOTTOMLEFT:
                    return this.StartResize(ResizeDir.Bottom | ResizeDir.Left);
                case Win32.HT.HTLEFT:
                    return this.StartResize(ResizeDir.Left);
            }

            return false;
        }
        #endregion

        #region ResizeOperations

        /// <summary>
        /// Begin resize
        /// </summary>
        /// <param name="resDir">The resize direction</param>
        /// <returns>Returns false if the the window will not stick on resize</returns>
        private bool StartResize(ResizeDir resDir)
        {
            if (this.stickOnResize)
            {
                this.resizeDirection = resDir;
                this.formRect = this.originalForm.Bounds;

                // Save the old bounds
                this.formOriginalRect = this.originalForm.Bounds;

                // Start capturing messages
                if (!this.originalForm.Capture)
                {
                    this.originalForm.Capture = true;
                }

                this.messageProcessor = this.resizeMessageProcessor;

                // Catch the message
                return true;
            }
            else
            {
                // Leave default processing !
                return false;
            }
        }

        /// <summary>
        /// Receives messages
        /// </summary>
        /// <param name="m">The message</param>
        /// <returns>Returns nothing</returns>
        private bool ResizeMsgProcessor(ref Message m)
        {
            if (!this.originalForm.Capture)
            {
                this.Cancel();
                return false;
            }

            switch (m.Msg)
            {
                case Win32.WM.WMLBUTTONUP:
                    {
                        // Ok, resize finished !!!
                        this.EndResize();
                        break;
                    }

                case Win32.WM.WMMOUSEMOVE:
                    {
                        this.mousePoint.X = (short)Win32.Bit.LoWord((int)m.LParam);
                        this.mousePoint.Y = (short)Win32.Bit.HiWord((int)m.LParam);
                        this.Resize(this.mousePoint);
                        break;
                    }

                case Win32.WM.WMKEYDOWN:
                    {
                        if ((int)m.WParam == Win32.VK.VKESCAPE)
                        {
                            // set back old size
                            this.originalForm.Bounds = this.formOriginalRect;
                            this.Cancel();
                        }

                        break;
                    }
            }

            return false;
        }

        /// <summary>
        /// End resize
        /// </summary>
        private void EndResize()
        {
            this.Cancel();
        }

        #endregion

        #region Resize Computing

        /// <summary>
        /// Resize window - calculation
        /// </summary>
        /// <param name="p">The form point to screen</param>
        private void Resize(Point p)
        {
            //// Disable resize on main
            ////if (originalForm.Name == "Main")
            ////{
            ////    if (Resizable == false)
            ////    {
            ////        return;
            ////    }
            ////}

            p = this.originalForm.PointToScreen(p);
            Screen activeScr = Screen.FromPoint(p);
            this.formRect = this.originalForm.Bounds;

            int iRight = this.formRect.Right;
            int iBottom = this.formRect.Bottom;

            // No normalize required. First strech the window to the new position
            if ((this.resizeDirection & ResizeDir.Left) == ResizeDir.Left)
            {
                this.formRect.Width = this.formRect.X - p.X + this.formRect.Width;
                this.formRect.X = iRight - this.formRect.Width;
            }

            if ((this.resizeDirection & ResizeDir.Right) == ResizeDir.Right)
            {
                this.formRect.Width = p.X - this.formRect.Left;
            }

            if ((this.resizeDirection & ResizeDir.Top) == ResizeDir.Top)
            {
                this.formRect.Height = this.formRect.Height - p.Y + this.formRect.Top;
                this.formRect.Y = iBottom - this.formRect.Height;
            }

            if ((this.resizeDirection & ResizeDir.Bottom) == ResizeDir.Bottom)
            {
                this.formRect.Height = p.Y - this.formRect.Top;
            }

            // This is the real new position. Now, try to snap it to different objects (first to screen)

            // CARE !!! We use "Width" and "Height" as Bottom & Right!! (C++ style)
            ////this.formOffsetRect = new Rectangle ( stickGap + 1, stickGap + 1, 0, 0 );
            this.formOffsetRect.X = stickGap + 1;
            this.formOffsetRect.Y = stickGap + 1;
            this.formOffsetRect.Height = 0;
            this.formOffsetRect.Width = 0;

            if (this.stickToScreen)
            {
                this.Resize_Stick(activeScr.WorkingArea, false);
            }

            if (this.stickToOther)
            {
                // now try to stick to other forms
                foreach (Form sw in globalStickyWindows)
                {
                    if (sw != this.originalForm)
                    {
                        this.Resize_Stick(sw.Bounds, true);
                    }
                }
            }

            // Fix (clear) the values that were not updated to stick
            if (this.formOffsetRect.X == stickGap + 1)
            {
                this.formOffsetRect.X = 0;
            }

            if (this.formOffsetRect.Width == stickGap + 1)
            {
                this.formOffsetRect.Width = 0;
            }

            if (this.formOffsetRect.Y == stickGap + 1)
            {
                this.formOffsetRect.Y = 0;
            }

            if (this.formOffsetRect.Height == stickGap + 1)
            {
                this.formOffsetRect.Height = 0;
            }

            // Compute the new form size
            if ((this.resizeDirection & ResizeDir.Left) == ResizeDir.Left)
            {
                // Left resize requires special handling of X & Width acording to MinSize and MinWindowTrackSize
                int iNewWidth = this.formRect.Width + this.formOffsetRect.Width + this.formOffsetRect.X;

                if (this.originalForm.MaximumSize.Width != 0)
                {
                    iNewWidth = Math.Min(iNewWidth, this.originalForm.MaximumSize.Width);
                }

                iNewWidth = Math.Min(iNewWidth, SystemInformation.MaxWindowTrackSize.Width);
                iNewWidth = Math.Max(iNewWidth, this.originalForm.MinimumSize.Width);
                iNewWidth = Math.Max(iNewWidth, SystemInformation.MinWindowTrackSize.Width);

                this.formRect.X = iRight - iNewWidth;
                this.formRect.Width = iNewWidth;
            }
            else
            {
                // Other resizes
                this.formRect.Width += this.formOffsetRect.Width + this.formOffsetRect.X;
            }

            if ((this.resizeDirection & ResizeDir.Top) == ResizeDir.Top)
            {
                int iNewHeight = this.formRect.Height + this.formOffsetRect.Height + this.formOffsetRect.Y;

                if (this.originalForm.MaximumSize.Height != 0)
                {
                    iNewHeight = Math.Min(iNewHeight, this.originalForm.MaximumSize.Height);
                }

                iNewHeight = Math.Min(iNewHeight, SystemInformation.MaxWindowTrackSize.Height);
                iNewHeight = Math.Max(iNewHeight, this.originalForm.MinimumSize.Height);
                iNewHeight = Math.Max(iNewHeight, SystemInformation.MinWindowTrackSize.Height);

                this.formRect.Y = iBottom - iNewHeight;
                this.formRect.Height = iNewHeight;
            }
            else
            {
                // All other resizing are fine 
                this.formRect.Height += this.formOffsetRect.Height + this.formOffsetRect.Y;
            }

            // Done !!
            this.originalForm.Bounds = this.formRect;
        }

        /// <summary>
        /// Occurs when resizing
        /// </summary>
        /// <param name="toRect">Rectangle to stick</param>
        /// <param name="bInsideStick">Is inside</param>
        private void Resize_Stick(Rectangle toRect, bool bInsideStick)
        {
            if (this.formRect.Right >= (toRect.Left - stickGap) && this.formRect.Left <= (toRect.Right + stickGap))
            {
                if ((this.resizeDirection & ResizeDir.Top) == ResizeDir.Top)
                {
                    if (Math.Abs(this.formRect.Top - toRect.Bottom) <= Math.Abs(this.formOffsetRect.Top) && bInsideStick)
                    {
                        // snap top to bottom
                        this.formOffsetRect.Y = this.formRect.Top - toRect.Bottom;
                    }
                    else if (Math.Abs(this.formRect.Top - toRect.Top) <= Math.Abs(this.formOffsetRect.Top))
                    {
                        // snap top to top
                        this.formOffsetRect.Y = this.formRect.Top - toRect.Top;
                    }
                }

                if ((this.resizeDirection & ResizeDir.Bottom) == ResizeDir.Bottom)
                {
                    if (Math.Abs(this.formRect.Bottom - toRect.Top) <= Math.Abs(this.formOffsetRect.Bottom) && bInsideStick)
                    {
                        // snap Bottom to top
                        this.formOffsetRect.Height = toRect.Top - this.formRect.Bottom;
                    }
                    else if (Math.Abs(this.formRect.Bottom - toRect.Bottom) <= Math.Abs(this.formOffsetRect.Bottom))
                    {
                        // snap bottom to bottom
                        this.formOffsetRect.Height = toRect.Bottom - this.formRect.Bottom;
                    }
                }
            }

            if (this.formRect.Bottom >= (toRect.Top - stickGap) && this.formRect.Top <= (toRect.Bottom + stickGap))
            {
                if ((this.resizeDirection & ResizeDir.Right) == ResizeDir.Right)
                {
                    if (Math.Abs(this.formRect.Right - toRect.Left) <= Math.Abs(this.formOffsetRect.Right) && bInsideStick)
                    {
                        // Stick right to left
                        this.formOffsetRect.Width = toRect.Left - this.formRect.Right;
                    }
                    else if (Math.Abs(this.formRect.Right - toRect.Right) <= Math.Abs(this.formOffsetRect.Right))
                    {
                        // Stick right to right
                        this.formOffsetRect.Width = toRect.Right - this.formRect.Right;
                    }
                }

                if ((this.resizeDirection & ResizeDir.Left) == ResizeDir.Left)
                {
                    if (Math.Abs(this.formRect.Left - toRect.Right) <= Math.Abs(this.formOffsetRect.Left) && bInsideStick)
                    {
                        // Stick left to right
                        this.formOffsetRect.X = this.formRect.Left - toRect.Right;
                    }
                    else if (Math.Abs(this.formRect.Left - toRect.Left) <= Math.Abs(this.formOffsetRect.Left))
                    {
                        // Stick left to left
                        this.formOffsetRect.X = this.formRect.Left - toRect.Left;
                    }
                }
            }
        }

        #endregion

        #region Move Operations

        /// <summary>
        /// Start moving form
        /// </summary>
        private void StartMove()
        {
            this.formRect = this.originalForm.Bounds;

            // Save original position
            this.formOriginalRect = this.originalForm.Bounds;

            // Start capturing messages
            if (!this.originalForm.Capture)
            {
                this.originalForm.Capture = true;
            }

            this.messageProcessor = this.moveMessageProcessor;
        }

        /// <summary>
        /// Receive moving messages
        /// </summary>
        /// <param name="m">The message</param>
        /// <returns>Returns... false</returns>
        private bool MoveMsgProcessor(ref Message m)
        {
            // Internal message loop
            if (!this.originalForm.Capture)
            {
                this.Cancel();
                return false;
            }

            switch (m.Msg)
            {
                case Win32.WM.WMLBUTTONUP:
                    {
                        // Ok, move finished!
                        this.EndMove();
                        break;
                    }

                case Win32.WM.WMMOUSEMOVE:
                    {
                        this.mousePoint.X = (short)Win32.Bit.LoWord((int)m.LParam);
                        this.mousePoint.Y = (short)Win32.Bit.HiWord((int)m.LParam);
                        this.Move(this.mousePoint);
                        break;
                    }

                case Win32.WM.WMKEYDOWN:
                    {
                        if ((int)m.WParam == Win32.VK.VKESCAPE)
                        {
                            // Set back old size
                            this.originalForm.Bounds = this.formOriginalRect;
                            this.Cancel();
                        }

                        break;
                    }
            }

            return false;
        }

        /// <summary>
        /// End moving the form
        /// </summary>
        private void EndMove()
        {
            this.Cancel();
        }

        #endregion

        #region Move Computing

        /// <summary>
        /// Move the form
        /// </summary>
        /// <param name="p">Move to this point</param>
        private void Move(Point p)
        {
            p = this.originalForm.PointToScreen(p);

            // Get the screen from the point
            Screen activeScr = Screen.FromPoint(p);

            if (!activeScr.WorkingArea.Contains(p))
            {
                p.X = this.NormalizeInside(p.X, activeScr.WorkingArea.Left, activeScr.WorkingArea.Right);
                p.Y = this.NormalizeInside(p.Y, activeScr.WorkingArea.Top, activeScr.WorkingArea.Bottom);
            }

            p.Offset(-this.offsetPoint.X, -this.offsetPoint.Y);

            // p is the exact location of the frame - so we can play with it to detect the new position acording to different bounds
            // This is the new positon of the form
            this.formRect.Location = p;

            // (more than) maximum gaps
            this.formOffsetPoint.X = stickGap + 1;
            this.formOffsetPoint.Y = stickGap + 1;

            if (this.stickToScreen)
            {
                this.Move_Stick(activeScr.WorkingArea, false);
            }

            // Now try to snap to other windows
            if (this.stickToOther)
            {
                foreach (Form sw in globalStickyWindows)
                {
                    if (sw != this.originalForm)
                    {
                        // Do not stick if the form is not visible...
                        if (sw.Visible != false)
                        {
                            this.Move_Stick(sw.Bounds, true);
                        }
                    }
                }
            }

            if (this.formOffsetPoint.X == stickGap + 1)
            {
                this.formOffsetPoint.X = 0;
            }

            if (this.formOffsetPoint.Y == stickGap + 1)
            {
                this.formOffsetPoint.Y = 0;
            }

            this.formRect.Offset(this.formOffsetPoint);

            this.originalForm.Bounds = this.formRect;
        }

        /// <summary>
        /// Move the windows
        /// </summary>
        /// <param name="toRect">Rectangle to try to snap to</param>
        /// <param name="bInsideStick">Allow snapping on the inside (window to screen)</param>
        private void Move_Stick(Rectangle toRect, bool bInsideStick)
        {
            // Disable move & stick simult. in main window; else it can cause ugly flicker...
            ////if (originalForm.Name == "MainForm")
            ////{
            ////    return;
            ////}

            // Compare distance from toRect to formRect and then with the found distances, compare the most closed position
            if (this.formRect.Bottom >= (toRect.Top - stickGap) && this.formRect.Top <= (toRect.Bottom + stickGap))
            {
                if (bInsideStick)
                {
                    if (Math.Abs(this.formRect.Left - toRect.Right) <= Math.Abs(this.formOffsetPoint.X))
                    {
                        // left 2 right
                        this.formOffsetPoint.X = toRect.Right - this.formRect.Left;
                    }

                    if (Math.Abs(this.formRect.Left + this.formRect.Width - toRect.Left) <= Math.Abs(this.formOffsetPoint.X))
                    {
                        // right 2 left
                        this.formOffsetPoint.X = toRect.Left - this.formRect.Width - this.formRect.Left;
                    }
                }

                if (Math.Abs(this.formRect.Left - toRect.Left) <= Math.Abs(this.formOffsetPoint.X))
                {
                    // snap left 2 left
                    this.formOffsetPoint.X = toRect.Left - this.formRect.Left;
                }

                if (Math.Abs(this.formRect.Left + this.formRect.Width - toRect.Left - toRect.Width) <= Math.Abs(this.formOffsetPoint.X))
                {
                    // snap right 2 right
                    this.formOffsetPoint.X = toRect.Left + toRect.Width - this.formRect.Width - this.formRect.Left;
                }
            }

            if (this.formRect.Right >= (toRect.Left - stickGap) && this.formRect.Left <= (toRect.Right + stickGap))
            {
                if (bInsideStick)
                {
                    if (Math.Abs(this.formRect.Top - toRect.Bottom) <= Math.Abs(this.formOffsetPoint.Y) && bInsideStick)
                    {
                        // Stick Top to Bottom
                        this.formOffsetPoint.Y = toRect.Bottom - this.formRect.Top;
                    }

                    if (Math.Abs(this.formRect.Top + this.formRect.Height - toRect.Top) <= Math.Abs(this.formOffsetPoint.Y) && bInsideStick)
                    {
                        // snap Bottom to Top
                        this.formOffsetPoint.Y = toRect.Top - this.formRect.Height - this.formRect.Top;
                    }
                }

                // try to snap top 2 top also
                if (Math.Abs(this.formRect.Top - toRect.Top) <= Math.Abs(this.formOffsetPoint.Y))
                {
                    // top 2 top
                    this.formOffsetPoint.Y = toRect.Top - this.formRect.Top;
                }

                if (Math.Abs(this.formRect.Top + this.formRect.Height - toRect.Top - toRect.Height) <= Math.Abs(this.formOffsetPoint.Y))
                {
                    // bottom 2 bottom
                    this.formOffsetPoint.Y = toRect.Top + toRect.Height - this.formRect.Height - this.formRect.Top;
                }
            }
        }
        #endregion

        #region Utilities

        /// <summary>
        /// Normalize position when moving the form
        /// </summary>
        /// <param name="iP1">Point (X or Y)</param>
        /// <param name="iM1">Working area left</param>
        /// <param name="iM2">Working area right</param>
        /// <returns>Returns working area</returns>
        private int NormalizeInside(int iP1, int iM1, int iM2)
        {
            if (iP1 <= iM1)
            {
                return iM1;
            }
            else if (iP1 >= iM2)
            {
                return iM2;
            }

            return iP1;
        }

        #endregion

        #region Cancel

        /// <summary>
        /// Cancel stick stuff
        /// </summary>
        private void Cancel()
        {
            this.originalForm.Capture = false;
            this.movingForm = false;
            this.resizingForm = false;
            this.messageProcessor = this.defaultMessageProcessor;
        }
        #endregion
    }
}
